/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.mybatis.builder.method;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import com.jianggujin.dbfly.mybatis.constant.JOrderStrategy;
import com.jianggujin.dbfly.util.JAssert;
import com.jianggujin.dbfly.util.JStringUtils;

public class JSort implements Iterable<JOrder>, Serializable {

    private static final JSort UNSORTED = JSort.by(new JOrder[0]);

    public static final JOrderStrategy DEFAULT_DIRECTION = JOrderStrategy.ASC;

    private final List<JOrder> orders;

    private JSort(JOrder... orders) {
        this(Arrays.asList(orders));
    }

    private JSort(List<JOrder> orders) {
        JAssert.notNull(orders, "Orders must not be null!");
        this.orders = Collections.unmodifiableList(orders);
    }

    private JSort(String... properties) {
        this(DEFAULT_DIRECTION, properties);
    }

    public JSort(JOrderStrategy direction, String... properties) {
        this(direction, properties == null ? new ArrayList<String>() : Arrays.asList(properties));
    }

    public JSort(JOrderStrategy direction, List<String> properties) {
        if (properties == null || properties.isEmpty()) {
            throw new IllegalArgumentException("You have to provide at least one property to sort by!");
        }

        this.orders = new ArrayList<JOrder>(properties.size());

        for (String property : properties) {
            this.orders.add(new JOrder(direction, property));
        }
    }

    public static JSort by(String... properties) {
        JAssert.notNull(properties, "Properties must not be null!");
        return properties.length == 0 ? JSort.unsorted() : new JSort(properties);
    }

    public static JSort by(List<JOrder> orders) {
        JAssert.notNull(orders, "Orders must not be null!");
        return orders.isEmpty() ? JSort.unsorted() : new JSort(orders);
    }

    public static JSort by(JOrder... orders) {
        JAssert.notNull(orders, "Orders must not be null!");
        return new JSort(orders);
    }

    public static JSort by(JOrderStrategy direction, String... properties) {
        JAssert.notNull(direction, "Direction must not be null!");
        JAssert.notNull(properties, "Properties must not be null!");
        JAssert.isTrue(properties.length > 0, "At least one property must be given!");

        List<JOrder> orders = new ArrayList<JOrder>(properties.length);
        for (String property : properties) {
            orders.add(new JOrder(direction, property));
        }
        return JSort.by(orders);
    }

    public static JSort unsorted() {
        return UNSORTED;
    }

    public JSort descending() {
        return withDirection(JOrderStrategy.DESC);
    }

    public JSort ascending() {
        return withDirection(JOrderStrategy.ASC);
    }

    public boolean isSorted() {
        return !orders.isEmpty();
    }

    public boolean isUnsorted() {
        return !isSorted();
    }

    public JSort and(JSort sort) {
        JAssert.notNull(sort, "Sort must not be null!");

        ArrayList<JOrder> these = new ArrayList<JOrder>(this.orders);

        for (JOrder order : sort) {
            these.add(order);
        }

        return JSort.by(these);
    }

    public JOrder getOrderFor(String property) {
        for (JOrder order : this) {
            if (order.getProperty().equals(property)) {
                return order;
            }
        }
        return null;
    }

    public Iterator<JOrder> iterator() {
        return this.orders.iterator();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (!(obj instanceof JSort)) {
            return false;
        }

        JSort that = (JSort) obj;
        return this.orders.equals(that.orders);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + orders.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return orders.isEmpty() ? "UNSORTED" : JStringUtils.collectionToCommaDelimitedString(orders);
    }

    private JSort withDirection(JOrderStrategy direction) {
        List<JOrder> items = new ArrayList<JOrder>(orders.size());
        for (JOrder order : orders) {
            items.add(new JOrder(direction, order.getProperty()));
        }
        return JSort.by(items);
    }
}
